package com.xbongbong.paas.service.impl;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.xbongbong.i18n.util.I18nMessageUtil;
import com.xbongbong.paas.enums.ExportStatusEnum;
import com.xbongbong.paas.log.MongoLogHelp;
import com.xbongbong.paas.log.constant.LogMemoConstant;
import com.xbongbong.paas.log.entity.LoginLogEntity;
import com.xbongbong.paas.log.entity.OperateLogEntity;
import com.xbongbong.paas.log.model.LoginLogModel;
import com.xbongbong.paas.log.model.OperateLogModel;
import com.xbongbong.paas.pojo.ExportStatusPojo;
import com.xbongbong.paas.pojo.dto.FormDataListDTO;
import com.xbongbong.paas.pojo.vo.ExportFormDataVO;
import com.xbongbong.paas.redis.PaasRedisHelper;
import com.xbongbong.paas.redis.RedisConstant;
import com.xbongbong.paas.redis.RedisPrefixConstant;
import com.xbongbong.paas.service.HandleImportService;
import com.xbongbong.paas.service.LogExportService;
import com.xbongbong.paas.thread.ThreadExportPoolBeanConfig;
import com.xbongbong.paas.toolbox.exception.XbbException;
import com.xbongbong.paas.toolbox.helper.PageHelper;
import com.xbongbong.paas.toolbox.util.BeanUtil;
import com.xbongbong.paas.toolbox.util.CollectionsUtil;
import com.xbongbong.paas.toolbox.util.DateTimeUtil;
import com.xbongbong.paas.toolbox.util.DateUtil;
import com.xbongbong.paas.toolbox.util.JsonHelperUtil;
import com.xbongbong.paas.toolbox.util.StringUtil;
import com.xbongbong.pro.constant.I18nStringConstant;
import com.xbongbong.pro.enums.errorcodes.ExportErrorCodeEnum;
import com.xbongbong.pro.enums.errorcodes.SystemErrorCodeEnum;
import com.xbongbong.pro.log.pojo.dto.LogExportDTO;
import com.xbongbong.pro.log.pojo.vo.LogExportVO;
import com.xbongbong.pro.log.pojo.vo.LoginLogVO;
import com.xbongbong.pro.log.pojo.vo.OperateLogVO;
import com.xbongbong.saas.constant.BasicConstant;
import com.xbongbong.saas.constant.StringConstant;
import com.xbongbong.saas.constant.export.ImportConstant;
import com.xbongbong.saas.enums.LoginLogFormAttrEnum;
import com.xbongbong.saas.enums.OperateLogFormAttrEnum;
import com.xbongbong.saas.enums.OperateModuleTypeEnum;
import com.xbongbong.saas.enums.OperateTypeEnum;
import org.apache.poi.ss.usermodel.CellStyle;
import org.apache.poi.ss.usermodel.DataFormat;
import org.apache.poi.ss.usermodel.Font;
import org.apache.poi.ss.usermodel.HorizontalAlignment;
import org.apache.poi.ss.usermodel.VerticalAlignment;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.ss.util.CellRangeAddress;
import org.apache.poi.xssf.streaming.SXSSFCell;
import org.apache.poi.xssf.streaming.SXSSFRow;
import org.apache.poi.xssf.streaming.SXSSFSheet;
import org.apache.poi.xssf.streaming.SXSSFWorkbook;
import org.apache.poi.xssf.usermodel.XSSFRichTextString;
import org.apache.poi.xssf.usermodel.XSSFSheet;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.openxmlformats.schemas.spreadsheetml.x2006.main.CTSheetDimension;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;

/**
 * 日志导出service实现类
 * @author 杨磊
 * @date 2022/10/13 10:56
 * @since v1.0
 * @version v1.0
 */
@Service("logExportService")
public class LogExportServiceImpl implements LogExportService {

    private static final Logger LOG = LoggerFactory.getLogger(LogExportServiceImpl.class);

    @Resource
    private LoginLogModel loginLogModel;
    @Resource
    private HandleImportService handleImportService;
    @Resource
    private PaasRedisHelper paasRedisHelper;
    @Resource
    private OperateLogModel operateLogModel;
    @Resource
    private MongoLogHelp mongoLogHelp;
    @Resource
    private ThreadExportPoolBeanConfig threadExportPoolBeanConfig;

    @Override
    public LogExportVO exportLog(LogExportDTO logExportDTO) throws XbbException {
        LogExportVO logExportVO = new LogExportVO();
        String corpid = logExportDTO.getCorpid();
        String userId = logExportDTO.getUserId();
        String hashKey = corpid + StringConstant.CROSS + userId;
        ExportStatusPojo exportStatusPojo = JsonHelperUtil.parseObject(paasRedisHelper.getValue(RedisPrefixConstant.EXPORT_STATUS_KEY, hashKey), ExportStatusPojo.class);
        if (exportStatusPojo != null && Objects.equals(exportStatusPojo.getFinish(), ExportStatusEnum.EXPORTING.getStatus())) {
            // 上次导出任务未还在进行中，未结束
            throw new XbbException(ExportErrorCodeEnum.API_ERROR_234004);
        }
        //操作内容是否模糊搜索
        boolean isMemoLike = StringUtil.isNotEmpty(logExportDTO.getMemoLike());
        //操作对象是否模糊搜索
        boolean isOperateObjectLike = StringUtil.isNotEmpty(logExportDTO.getOperateObject());
        if(isMemoLike || isOperateObjectLike){
            throw new XbbException(ExportErrorCodeEnum.API_ERROR_234010);
        }
        // 上次导出状态缓存清除
        paasRedisHelper.removeValue(RedisPrefixConstant.EXPORT_STATUS_KEY,hashKey);
        //筛个假的状态
        exportStatusPojo = new ExportStatusPojo(40000);
        paasRedisHelper.setValue(RedisPrefixConstant.EXPORT_STATUS_KEY,hashKey, exportStatusPojo, RedisConstant.SHORT_DURATION);
        ExportStatusPojo finalExportStatusPojo = exportStatusPojo;
        Runnable runnable =  () -> {
            try {
                executeExportLog(logExportDTO, finalExportStatusPojo,hashKey);
            } catch (XbbException e) {
                ExportStatusPojo failedExportStatus = JsonHelperUtil.parseObject(Objects.toString(paasRedisHelper.getValue(RedisPrefixConstant.EXPORT_STATUS_KEY, hashKey), JSON.toJSONString(new ExportStatusPojo())), ExportStatusPojo.class);
                failedExportStatus.setFinish(ExportStatusEnum.FAIL.getStatus());
                failedExportStatus.setErrorMsg(I18nMessageUtil.getMessage(I18nStringConstant.EXPORT_FAILED));
                paasRedisHelper.setValue(RedisPrefixConstant.EXPORT_STATUS_KEY, hashKey, failedExportStatus, RedisConstant.SHORT_DURATION);
                e.printStackTrace();
            }
        };
        threadExportPoolBeanConfig.exportThreadPool().execute(runnable);
        return logExportVO;
    }

    /**
     * @description 异步执行导出逻辑
     * @author yanglei
     * @date 2022/11/25 9:19
     * @param logExportDTO
     * @return LogExportVO
     */
    public void executeExportLog(LogExportDTO logExportDTO,ExportStatusPojo exportStatusPojo,String hashKey) throws XbbException {
        String loginUserName = logExportDTO.getLoginUserName();
        Integer logExportType = logExportDTO.getLogExportType();
        String corpid = logExportDTO.getCorpid();
        logExportDTO.setPage(BasicConstant.ONE);
        logExportDTO.setPageSize(1000);
        JSONArray dataList = new JSONArray();
        String menuName = "";
        OperateModuleTypeEnum operateModuleTypeEnum = null;
        if(Objects.equals(logExportType, BasicConstant.ONE)){
            dataList = operateLogList(logExportDTO);
            menuName = I18nMessageUtil.getMessage(StringConstant.OPERATE_LOG_EXPORT_TITLE);
            operateModuleTypeEnum = OperateModuleTypeEnum.OPERATE_LOG;
        }else if (Objects.equals(logExportType,BasicConstant.TWO)){
            dataList = loginLogList(logExportDTO);
            menuName = I18nMessageUtil.getMessage(StringConstant.LOGIN_LOG_EXPORT_TITLE);
            operateModuleTypeEnum = OperateModuleTypeEnum.LOGIN_LOG;
        }
        //添加本次导出任务的数据量
        exportStatusPojo.setTotalNum(dataList.size());
        paasRedisHelper.setValue(RedisPrefixConstant.EXPORT_STATUS_KEY,hashKey, exportStatusPojo, RedisConstant.SHORT_DURATION);
        //获取excel对象
        Workbook workbook = createLogExcel(dataList, logExportType);
        //上传文件
        FormDataListDTO formDataListDTO = new FormDataListDTO();
        BeanUtil.copyProperties(logExportDTO, formDataListDTO);
        exportLogUpload(workbook,menuName,formDataListDTO);
        //日志处理
        String logMemo = String.format(I18nMessageUtil.getMessage(LogMemoConstant.LOG_MEMO_USER_EXPORT_LOG),loginUserName,menuName,dataList.size());
        mongoLogHelp.buildLogWithoutAsync(corpid,logExportDTO.getUserId(),loginUserName,operateModuleTypeEnum,
                OperateTypeEnum.EXPORT,null,menuName,logMemo,logExportDTO.getHttpHeader());
    }


    /**
     * 上传日志导出的表格
     * @param workbook
     * @param menuName
     * @param formDataListDTO
     * @throws XbbException
     */
    private void exportLogUpload(Workbook workbook,String menuName,FormDataListDTO formDataListDTO) throws XbbException {
        String[] split = formDataListDTO.getLocale().split("_");
        LocaleContextHolder.setLocale(new Locale(split[0], split[1]));
        String hashKey = formDataListDTO.getCorpid() + "_" + formDataListDTO.getUserId();
        try {
            ExportFormDataVO exportFormDataVO = new ExportFormDataVO();
            String excelName = menuName + DateTimeUtil.getString(DateTimeUtil.SDFDate) + ImportConstant.EXCEL_SUFFIX;
            exportFormDataVO.setWorkbook(workbook);
            exportFormDataVO.setName(excelName);
            // OSS上传文件
            handleImportService.exportAsync(formDataListDTO, exportFormDataVO, ImportConstant.EXCEL_SUFFIX);
        } catch (XbbException e) {
            LOG.error("com.xbongbong.paas.service.impl.LogExportServiceImpl.exportLogUpload", e);
            ExportStatusPojo exportStatusPojo = JsonHelperUtil.parseObject(Objects.toString(paasRedisHelper.getValue(RedisPrefixConstant.EXPORT_STATUS_KEY, hashKey), JSON.toJSONString(new ExportStatusPojo())), ExportStatusPojo.class);
            exportStatusPojo.setFinish(ExportStatusEnum.FAIL.getStatus());
            exportStatusPojo.setErrorMsg(I18nMessageUtil.getMessage(I18nStringConstant.EXPORT_FAILED));
            paasRedisHelper.setValue(RedisPrefixConstant.EXPORT_STATUS_KEY, hashKey, exportStatusPojo, RedisConstant.SHORT_DURATION);
            throw e;
        } catch (Exception e) {
            LOG.error("日志导出上传文件出错", e);
            ExportStatusPojo exportStatusPojo = JsonHelperUtil.parseObject(Objects.toString(paasRedisHelper.getValue(RedisPrefixConstant.EXPORT_STATUS_KEY, hashKey), JSON.toJSONString(new ExportStatusPojo())), ExportStatusPojo.class);
            exportStatusPojo.setFinish(ExportStatusEnum.FAIL.getStatus());
            exportStatusPojo.setErrorMsg(I18nMessageUtil.getMessage(I18nStringConstant.EXPORT_FAILED));
            paasRedisHelper.setValue(RedisPrefixConstant.EXPORT_STATUS_KEY, hashKey, exportStatusPojo, RedisConstant.SHORT_DURATION);
            throw new XbbException(SystemErrorCodeEnum.API_ERROR_100001);
        } finally {
            closeWorkBookException(workbook);
        }
    }


    /**
     * 操作日志查询最大查询50000条, 因为日志查询限制了每页数量小于200，所以这边分页查询是每页100条
     * @param logExportDTO
     * @return
     */
    private JSONArray operateLogList(LogExportDTO logExportDTO) throws XbbException {
        List<OperateLogVO> operateLogVOs = new ArrayList<>();
        Integer page = logExportDTO.getPage();
        Integer pageSize = logExportDTO.getPageSize();
        Map<String, Object> param = BeanUtil.convertBean2Map(logExportDTO, true);
        //排序
        param.put("orderByStr", "addTime DESC");
        while (operateLogVOs.size() <= 40000){
            try {
                PageHelper pageHelper = new PageHelper(page, pageSize);
                param.put("pageNum", pageHelper.getPageSize());
                param.put("start", (pageHelper.getCurrentPageNum() - 1) * pageHelper.getPageSize());
                List<OperateLogEntity> entitys = (List<OperateLogEntity>) operateLogModel.findEntitys(param,true);
                if (CollectionsUtil.isEmpty(entitys)){
                    break;
                }
                for (OperateLogEntity operateLog : entitys) {
                    OperateLogVO operateLogVO = new OperateLogVO();
                    BeanUtil.copyProperties(operateLog, operateLogVO);
                    String addTimeStr = DateUtil.getString(operateLog.getAddTime(), new SimpleDateFormat("yyyy-MM-dd HH:mm"));
                    operateLogVO.setAddTime(addTimeStr);
                    operateLogVOs.add(operateLogVO);
                }
                LOG.warn("导出操作日志查询到第{}页,每页{}条",page,pageSize);
                page++;
            }catch (XbbException e){
                LOG.error("导出操作日志查询出错",e);
                page++;
            }
        }
        return JSONArray.parseArray(JSON.toJSONString(operateLogVOs));
    }

    /**
     * 登录日志查询最大查询50000条, 因为日志查询限制了每页数量小于200，所以这边分页查询是每页100条
     * @param logExportDTO
     * @return
     * @throws XbbException
     */
    private JSONArray loginLogList(LogExportDTO logExportDTO) throws XbbException {
        List<LoginLogVO> loginLogVOs = new ArrayList<>();
        Integer page = logExportDTO.getPage();
        Integer pageSize = logExportDTO.getPageSize();
        Map<String, Object> param = BeanUtil.convertBean2Map(logExportDTO, true);
        //排序
        param.put("orderByStr", "addTime DESC");
        while (loginLogVOs.size() <= 40000){
            try {
                PageHelper pageHelper = new PageHelper(page, pageSize);
                param.put("pageNum", pageHelper.getPageSize());
                param.put("start", (pageHelper.getCurrentPageNum() - 1) * pageHelper.getPageSize());
                List<LoginLogEntity> entitys = (List<LoginLogEntity>) loginLogModel.findEntitys(param,true);
                if (CollectionsUtil.isEmpty(entitys)){
                    break;
                }
                for (LoginLogEntity loginLog : entitys) {
                    LoginLogVO loginLogVO = new LoginLogVO();
                    BeanUtil.copyProperties(loginLog, loginLogVO);
                    String addTimeStr = DateUtil.getString(loginLog.getAddTime(), new SimpleDateFormat("yyyy-MM-dd HH:mm"));
                    loginLogVO.setAddTime(addTimeStr);
                    loginLogVOs.add(loginLogVO);
                }
                LOG.warn("导出登录日志查询到第{}页,每页{}条",page,pageSize);
                page++;
            }catch (XbbException e){
                LOG.error("导出登录日志查询出错：",e);
                page++;
            }
        }
        return JSONArray.parseArray(JSON.toJSONString(loginLogVOs));
    }


    private Workbook createLogExcel(JSONArray logDataList,Integer logExportType) throws XbbException {
        //标题和表头字段
        String exportTitle = StringConstant.OPERATE_LOG_EXPORT_TITLE;
        List<String> headList = OperateLogFormAttrEnum.getAllEnumName();
        List<String> logValueAlias = OperateLogFormAttrEnum.getAllEnumAlias();
        if(Objects.equals(logExportType,BasicConstant.TWO)){
            exportTitle = StringConstant.LOGIN_LOG_EXPORT_TITLE;
            headList = LoginLogFormAttrEnum.getAllEnumName();
            logValueAlias = LoginLogFormAttrEnum.getAllEnumAlias();
        }
        //列数
        int columnNum = headList.size();
        XSSFWorkbook workbook = new XSSFWorkbook();
        XSSFSheet xssfSheet = workbook.createSheet();
        CTSheetDimension dimension = xssfSheet.getCTWorksheet().getDimension();
        dimension.setRef("A1:A" + (logDataList.size()));
        SXSSFWorkbook wb = new SXSSFWorkbook(workbook, 500);
        SXSSFSheet sheet = wb.getSheetAt(0);
        sheet.setDefaultColumnWidth(20);
        // 样式对象
        CellStyle titleStyle = wb.createCellStyle();
        // 垂直
        titleStyle.setVerticalAlignment(VerticalAlignment.CENTER);
        // 水平
        titleStyle.setAlignment(HorizontalAlignment.CENTER);
        // 设置标题字体格式
        Font fontTitle = wb.createFont();
        // 设置字体大小
        fontTitle.setFontHeightInPoints(ImportConstant.FONT_SIZE);
        // 设置字体
        fontTitle.setFontName(ImportConstant.FONT_COURIER_NEW);
        titleStyle.setFont(fontTitle);
        // 设置是否能够换行，能够换行为true
        titleStyle.setWrapText(false);
        //第一行表格标题
        SXSSFRow titleRow = sheet.createRow(0);
        SXSSFCell titleCell = titleRow.createCell(0);
        titleCell.setCellStyle(titleStyle);
        titleCell.setCellValue(formatRichTextValue(I18nMessageUtil.getMessage(exportTitle), workbook));
        //合并第一行
        if (columnNum > 1) {
            sheet.addMergedRegion(new CellRangeAddress(0, 0, 0, columnNum  - 1));
        }
        //第二行表头字段
        SXSSFRow rowSecond = sheet.createRow(1);
        for(int i = 0; i < columnNum;i++){
            SXSSFCell headCell = rowSecond.createCell(i);
            headCell.setCellValue(formatRichTextValue(I18nMessageUtil.getMessage(headList.get(i)), workbook));
        }
        //下面是数据
        int rowIntNum = sheet.getLastRowNum() + 1;
        if(CollectionsUtil.isEmpty(logDataList)){
            //如果数据为空，导出一个空模板
            return wb;
        }
        CellStyle cellStyle = wb.createCellStyle();
        DataFormat format = wb.createDataFormat();
        cellStyle.setDataFormat(format.getFormat("@"));
        for(Object obj : logDataList){
            SXSSFRow row = sheet.createRow(rowIntNum);
            JSONObject json = (JSONObject) obj;
            for (int j = 0; j < columnNum; j++) {
                String attr = logValueAlias.get(j);
                String value = json.getString(attr);
                if (StringUtil.isNotEmpty(value)){
                    SXSSFCell cell = row.createCell(j);
                    cell.setCellValue(formatRichTextValue(value,wb));
                    sheet.setDefaultColumnStyle(j,cellStyle);
                }
            }
            rowIntNum++;
        }
        return wb;
    }

    /**
     * 富文本字段类型字体样式
     * @param value
     * @param workbook
     * @return
     */
    private XSSFRichTextString formatRichTextValue(String value,Workbook workbook){
        XSSFRichTextString richString = new XSSFRichTextString(value);
        Font font = workbook.createFont();
        font.setFontHeightInPoints((short) 12);
        font.setFontName("宋体");
        richString.applyFont(font);
        return richString;
    }

    /**
     * 关闭workbook
     * @param workbook
     */
    private void closeWorkBookException( Workbook workbook) {
        if (Objects.nonNull(workbook)) {
            try {
                workbook.close();
            } catch (IOException e1) {
                LOG.error("关闭workbook错误", e1);
            }catch (Exception e1) {
                LOG.error("关闭workbook错误", e1);
            }
        }
    }
}
